package com.wechat.websocket.netty;

import com.wechat.entity.config.AppConfig;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @return
 * @Description ws 启动类
 * 是一个单例服务，整个应用启动一次，监听指定端口，等待所有客户端的连接
 */
@Component
public class NettyWebSocketStarter implements Runnable{
    private static final Logger logger = LoggerFactory.getLogger(NettyWebSocketStarter.class);
    private static EventLoopGroup bossGroup = new NioEventLoopGroup(1);//boss线程组，用于处理客户端的连接请求
    private static EventLoopGroup workerGroup = new NioEventLoopGroup();//worker线程组，用于处理业务逻辑（读写）
    @Resource
    private HandlerWebSocket handlerWebSocket;//static不需要加，因为添加static后Spring无法正确注入依赖
    @Autowired
    private AppConfig appConfig;


    @PreDestroy//销毁方法
    //由Spring容器在Bean销毁时，会自动调用该方法
    public void destroy() {
        logger.info("正在关闭Netty WS服务器");
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }

//    @Async//异步执行
//    public void startNetty() {
//
//    }

    @Override
    public void run() {
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap()//创建服务端启动类
                    .group(bossGroup, workerGroup)//添加两个线程组
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .childHandler(new ChannelInitializer<Channel>() {//用于添加通道处理器，处理I/O事件
                        /**
                         * 初始化通道
                         * @param channel
                         * @throws Exception
                         */
                        @Override
                        protected void initChannel(Channel channel) throws Exception {
                            ChannelPipeline pipeline = channel.pipeline();//维护一个ChannelHandler链，处理入站与出站数据
                            // 使用 http 解码器:将原始的字节流解码为http请求对象(httpRequest[请求行+请求头],httpContent[请求体])
                            //                将http响应对象编码为原始的字节流（httpResponse,httpContent）
                            pipeline.addLast(new HttpServerCodec());
                            //聚合解码 将分散的http消息片段，httpRequest/httpContent/lastHttpContent 合并为 fullHttpRequest[包含完整的请求头与请求体]
                            //保证接收http的完整性
                            pipeline.addLast(new HttpObjectAggregator(64 * 1024));//设置最大聚合字节数

                            //添加心跳检测：检测连接的状态（空闲状态---触发IdleStateEvent通知后续处理器处理空闲连接）：当读超时，则触发IdleStateEvent
                            //参数：读超时（服务端一定时间内未接收到被测试端（客户端）消息），写超时（服务端一定时间内向客户端发送消息），读写超时（所有类型超时时间），时间单位
                            //为什么只设置读超时？ 答：因为服务端一般不会发送消息给客户端，需要客户端主动发送请求
                            //设置服务端30秒内没有接收到客户端的信息就会触发IdleStateEvent
                            pipeline.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS));
                            //添加心跳超时处理
                            pipeline.addLast(new HandlerHeartBeat());
                            //将http请求升级为websocket请求
                            pipeline.addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536, true, true, 10000L));
                            //添加websocket处理
                            pipeline.addLast(handlerWebSocket);
                        }
                    });
            //绑定ws端口
            Integer wsPort = appConfig.getWsPort();//获取默认状态的ws绑定端口
            String wsPortStr = System.getProperty("server.websocket.port");//获取自定义的ws绑定端口
            if(wsPortStr != null && !wsPortStr.isEmpty()){//如果自定义的ws绑定端口不为空，则使用自定义的ws绑定端口
                wsPort = Integer.parseInt(wsPortStr);
            }
            if (wsPort == null) {
                wsPort = 5051; // 默认端口
            }
            /*
            channelFuture是接收netty异步I/O操作的结果
            channelFuture.channel()获取关联的通道对象，用于获取通道的属性，如：端口号、ip地址、通道状态等信息
            执行.sync()可以阻塞当前线程，直到操作完成，通道关闭，或者使用.addListener()方法添加监听器，监听通道关闭事件
             */
            ChannelFuture channelFuture = serverBootstrap.bind(wsPort).sync();//阻塞当前线程直到服务器甬道绑定指定端口完成
            logger.info("netty启动成功，端口号：{}", channelFuture.channel().localAddress());
            channelFuture.channel().closeFuture().sync();//阻塞当前线程直到服务器甬道关闭

        } catch (Exception e) {
            logger.error("启动失败：{}", e.getMessage(), e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
